package opgave2;

import java.io.PrintStream;
import java.util.Scanner;
import java.util.regex.Pattern;

public class Program {

	PrintStream out;

	Tabel<Identifier, Verzameling<NatuurlijkGetal>> tabel;

	Program() {
		out = new PrintStream(System.out);

		tabel = new Tabel<Identifier, Verzameling<NatuurlijkGetal>>();
	}

	void start() {
		Scanner in = new Scanner(System.in).useDelimiter("");
		while(in.hasNext()) {
			try {
				statement(new Scanner(in.nextLine()));
			} catch (VPException e) {
				out.print(e.getMessage() + "\n");
			}
		}
	}

	// Statement
	void statement(Scanner input) throws VPException {
		input.useDelimiter("");
		if(isScannerLeeg(input)) {
			throw new VPException("De input is leeg.");
		} else if(nextCharIsLetter(input)) {
			assignment(input);
		} else if (nextCharIs(input, '?')) {
			printStatement(input);
		} else if (nextCharIs(input, '/')) {
			// Commentaar wordt niet verwerkt.
		} else {
			throw new VPException("De invoer van de statement begint verkeerd.");
		}
	}

	// Assignment
	void assignment(Scanner input) throws VPException {
		// Lees identifier naar id
		Identifier id = leesIdentifier(input);
		// Lees de '=' en de spaties weg en check vervolgens of er nog wel iets in staat.
		leesTeken(input, '=');
		// Verwerk de expressie
		Verzameling<NatuurlijkGetal> verz = expressie(input);

		if(!isScannerLeeg(input)) {
			throw new VPException("Er mag niets na de expressie staan.");
		}
		// Voeg de data toe aan de tabel
		tabel.add(id, verz);
	}

	// Print Statement
	void printStatement(Scanner input) throws VPException {
		// '?' en spaties weglezen
		leesTeken(input, '?');
		
		// Expressie inlezen
		Verzameling<NatuurlijkGetal> verz = expressie(input);
		
		// Verzameling afdrukken
		printVerzameling(verz);
	}

	// Identifier
	Identifier leesIdentifier(Scanner input) throws VPException {
		Identifier result = new Identifier();
		
		if(!isScannerLeeg(input) && nextCharIsLetter(input)) {
			result.init(nextChar(input));
		} else {
			throw new VPException("De identifier begint niet met een letter");
		}

		while(nextCharIsDigit(input) || nextCharIsLetter(input)) {
			// Volgende char is een letter of cijfer
			result.addChar(nextChar(input));
		}

		return result;
	}

	// Expressie
	Verzameling<NatuurlijkGetal> expressie(Scanner input) throws VPException {
		input.useDelimiter("");
		Verzameling<NatuurlijkGetal> resultaat;
		
		resultaat = term(input);
		boolean running = true;
		while(!isScannerLeeg(input) && running) {
			if(nextCharIs(input, '+')) {
				nextChar(input);
				resultaat = resultaat.vereniging(term(input));
			} else if(nextCharIs(input, '-')) {
				nextChar(input);
				resultaat = resultaat.verschil(term(input));
			} else if(nextCharIs(input, '|')) {
				nextChar(input);
				resultaat = resultaat.symvers(term(input));
			} else if(!nextCharIs(input, '+') && !nextCharIs(input, '|') && !nextCharIs(input, '-')){
				throw new VPException("Foute expressie, er werd een additieve operator verwacht");
			} else{
				running = false;
			}
		}
		
		return resultaat;
	}
	
	// Term
	Verzameling<NatuurlijkGetal> term(Scanner input) throws VPException {
		Verzameling<NatuurlijkGetal> resultaat;
		
		resultaat = factor(input);
		
		while(!isScannerLeeg(input) && nextCharIs(input, '*')) {
			nextChar(input);
			resultaat = resultaat.doorsnede(factor(input));
		}
		
		return resultaat;
	}
	
	// Factor
	Verzameling<NatuurlijkGetal> factor(Scanner input) throws VPException{
		Verzameling<NatuurlijkGetal> resultaat;
		
		if(isScannerLeeg(input)) {
			throw new VPException("Factor is leeg.");
		}
		
		if(nextCharIsLetter(input)) {
			// Lees identifier en bijbehorende verzameling
			Identifier id = leesIdentifier(input);
			if(tabel.contains(id)) {
				resultaat = tabel.get(id);
			} else {
				throw new VPException("De opgevraagde variabele/verzameling is niet gedefineerd.");
			}
		} else if (nextCharIs(input, '{')) {
			// lees een verzameling
			nextChar(input);
			resultaat = verzameling(new Scanner(inputTotChar(input, '}')));
			if(isScannerLeeg(input)) {
				throw new VPException("De verzameling moet eindigen met een '}'");
			}
			nextChar(input);
		} else if (nextCharIs(input, '(')) {
			// bepaal de uitkomst van de complexe factor
			nextChar(input);
			resultaat = expressie(new Scanner(complexeFactor(input)));
			nextChar(input);
		} else {
			throw new VPException("Factor is verkeerd gedefineerd.");
		}
		
		return resultaat;
	}
	
	// Complexe Factor
	String complexeFactor(Scanner input) throws VPException {
		String resultaat = "";
		int haakjesOpen = 1;
		
		while(haakjesOpen>0) {
			if(!input.hasNext()) {
				throw new VPException("Er zijn meer openende haakjes dan sluitende.");
			}
			if(nextCharIs(input, '(')) {
				haakjesOpen++;
			} else if (nextCharIs(input, ')')) {
				haakjesOpen--;
			}
			if(haakjesOpen > 0) {
				resultaat += nextChar(input);
			}			
		}
		
		return resultaat;
	}
	
	// Verzameling
	Verzameling<NatuurlijkGetal> verzameling(Scanner input) throws VPException {
		input.useDelimiter("");
		Verzameling<NatuurlijkGetal> resultaat = new Verzameling<NatuurlijkGetal>();

		Scanner natuurlijkGetalScanner;
		
		while(!isScannerLeeg(input)) {
			String natuurlijkGetal = "";
			while(!input.hasNext(Pattern.quote(",")) && input.hasNext()) {
				natuurlijkGetal += input.next();
			}
			natuurlijkGetalScanner = new Scanner(natuurlijkGetal);
			if(!isScannerLeeg(natuurlijkGetalScanner)) {
				resultaat.add(natuurlijkGetal(natuurlijkGetalScanner));
			}
			if(!isScannerLeeg(input)) {
				// Komma weglezen als de scanner nog niet leeg is.
				nextChar(input);
			}
		}
		
		return resultaat;
	}
	
	// Natuurlijk Getal
	NatuurlijkGetal natuurlijkGetal (Scanner input) throws VPException {
		input.useDelimiter("");
		NatuurlijkGetal resultaat = new NatuurlijkGetal();
		if(nextCharIsDigit(input)) {
			resultaat.init(nextChar(input));
		}
		
		while(!input.hasNext(Pattern.quote(" ")) && nextCharIsDigit(input) && input.hasNext()) {
			resultaat.voegNummerToe(input.next().charAt(0));
		}
		
		if(!isScannerLeeg(input)) {
			throw new VPException("Elk natuurlijk getal moet gescheiden worden met een komma en mag geen andere tekens bevatten dan cijfers.");
		}
		
		return resultaat;
	}

	void leesTeken(Scanner in, char c) throws VPException {
		while(in.hasNext(Pattern.quote(" "))) {
			in.next();
		}
		if(nextCharIs(in, c)) {
			in.next();
		} else {
			throw new VPException("Er werd een '" + c + "' verwacht maar een ander teken is tegengekomen.");
		}
	}
	
	void printVerzameling(Verzameling<NatuurlijkGetal> verzameling){
		while(!verzameling.isEmpty()){
			String str = "";
			NatuurlijkGetal ng = verzameling.take();
			for(int i = 0; i < ng.lengte(); i++) {
				str += ng.geefNummerOpPlaats(i);
			}
			out.printf("%s ", str);
		}
		out.print("\n");
	}

	public static void main(String[] args) {
		new Program().start();
	}

	// Hulpmethoden, voor deze methoden moet de delimiter op "" staan.
	
	boolean isScannerLeeg(Scanner in) {
		// Deze methode verwijdert ook alle spaties tot het eerstvolgende teken wat geen spatie is.
		if(!in.hasNext()) {
			return true;
		} else {
			while(in.hasNext(Pattern.quote(" "))) {
				in.next();
			}
			return !in.hasNext();
		}
	}
	
	String inputTotChar(Scanner in, char c) {
		String result = "";
		while (in.hasNext()) {
			if(nextCharIs(in, c)) {
				return result;
			}
			result += nextChar(in);
		}
		return result;
	}

	char nextChar(Scanner in) {
		return in.next().charAt(0);
	}

	boolean nextCharIs(Scanner in, char c) {
		return in.hasNext(Pattern.quote(c+""));
	}

	boolean nextCharIsDigit (Scanner in) {
		return in.hasNext("[0-9]");
	}

	boolean nextCharIsLetter (Scanner in) {
		return in.hasNext("[a-z,A-Z]");
	}
}